#include "stdafx.h"
#include "NetworkThread.h"

#ifdef _WIN32
#ifdef _DEBUG
	#define _CRTDBG_MAP_ALLOC
	#include <stdlib.h>
	#include <crtdbg.h>
	#undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
	#define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
	#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif


namespace tlnet {

//void NetworkThread(void *param) {
unsigned __stdcall NetworkThread(void *param) {
	NetworkThreadStruct *nts = (NetworkThreadStruct*)param;
	NetworkManager *nm = nts->nm;
	Connection *conn = nts->conn;


	// If this is a server listen port
	if(conn->settings.isSet("isListen")) {
		// server, listen to port and add clients
		NetworkThreadListen(nm, conn);
	}
	// Otherwise, must be a general network socket
	else {
		// watch for data on the network socket
		NetworkThreadSocket(nm, conn);
	}

	// free up no longer needed allocated memory
	delete nts;

	// end the thread
	_endthreadex(0);
	return 0;
}


/**
 * Watches for incoming connections on the listen socket, adding clients
 */
void NetworkThreadListen(NetworkManager *nm, Connection *conn) {
    int iResult;
	std::string lower = "";


    struct addrinfo *result = NULL;
    struct addrinfo hints;


	// remember the associated address
	hostent* localhost = gethostbyname("");
	conn->set("host", localhost->h_name);
	conn->set("hostip", inet_ntoa (*(struct in_addr *)*localhost->h_addr_list));

	// if ipaddress is 127.0.0.1 then set the hostname to be localhost
/*	if(!strcmp(conn->get("hostip"), "127.0.0.1")) {
		conn->set("host", "localhost");
	}
*/
	// convert the host to lowercase
	lower = conn->get("host");
	std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
	conn->set("host", (char*)lower.c_str());

	// put together structure to pull address info
    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
	if(conn->settings.isSet("bindaddr")) {
		iResult = getaddrinfo(conn->get("bindaddr"), conn->settings.get("port"), &hints, &result);
	}
	else {
		iResult = getaddrinfo(NULL, conn->settings.get("port"), &hints, &result);
	}
    if(iResult != 0) {
#ifdef _DEBUG
        printf("getaddrinfo failed with error: %d\n", iResult);
#endif // _DEBUG

		// finished initializing
		conn->settings.del("isInit");
        return;
    }

    // Create a SOCKET for connecting to server
	conn->socket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	if(conn->socket == INVALID_SOCKET) {
#ifdef _DEBUG
        printf("socket failed with error: %ld\n", WSAGetLastError());
#endif // _DEBUG
        freeaddrinfo(result);

		// finished initializing
		conn->settings.del("isInit");
		return;
    }

/*	// Open port anyways, even if it wasn't closed down cleanly before
	bool bOptVal = true;
	setsockopt(conn->socket, SOL_SOCKET, SO_REUSEADDR, (char*)&bOptVal, sizeof(bOptVal));
*/
    // Setup the TCP listening socket
	iResult = bind(conn->socket, result->ai_addr, (int)result->ai_addrlen);
    if(iResult == SOCKET_ERROR) {
#ifdef _DEBUG
        printf("bind failed with error: %d\n", WSAGetLastError());
#endif // _DEBUG
        freeaddrinfo(result);
		closesocket(conn->socket);

		// finished initializing
		conn->settings.del("isInit");
		return;
    }

    freeaddrinfo(result);

	iResult = listen(conn->socket, SOMAXCONN);
    if(iResult == SOCKET_ERROR) {
#ifdef _DEBUG
        printf("listen failed with error: %d\n", WSAGetLastError());
#endif // _DEBUG
		closesocket(conn->socket);

		// finished initializing
		conn->settings.del("isInit");
		return;
    }

	// finished initializing
	conn->settings.del("isInit");

	// Set the isReady event to signaled
	SetEvent(conn->hReady);


	/* client address */
	struct sockaddr_in clientaddr;
	int addrlen = sizeof(clientaddr);

	SOCKET socket;

	while((socket = accept(conn->socket, (struct sockaddr*) &clientaddr, &addrlen)) != -1) {
		if(socket != INVALID_SOCKET) {
			// resolve ip address of client
//			struct in_addr addr = { 0 };
//			addr.s_addr = inet_addr( inet_ntoa(clientaddr.sin_addr) );
			localhost = gethostbyaddr(
				(char*)&clientaddr.sin_addr, 4, clientaddr.sin_family);

			// add client
			Connection *c = nm->add();
			c->settings.set("host", localhost->h_name);
			c->settings.set("hostip", inet_ntoa(clientaddr.sin_addr));
			c->settings.set("port", conn->settings.get("port"));
			c->settings.set("isAlive", "true");
			c->settings.set("isNew", "true");
			c->socket = socket;

			// convert the host to lowercase
			lower = c->get("host");
			std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
			c->set("host", (char*)lower.c_str());

			NetworkThreadStruct *nts = new NetworkThreadStruct;
			nts->nm = nm;
			nts->conn = c;

			unsigned threadId;
			c->thread = _beginthreadex(NULL, 0, &NetworkThread, nts, 0, &threadId);
			if(c->thread == 0) {
#ifdef _DEBUG
				printf("[NetworkManager::NetworkThreadListen] -> _beginthreadex failed\n");
#endif // _DEBUG
			}
		}
		else {
#ifdef _DEBUG
			printf("accept() failed, D.O.S. in progress?\n");
#endif // _DEBUG
			
			// client dropped connection, disable
			conn->settings.del("isAlive");
			conn->settings.set("isDel", "true");

			// Set the isReady event to signaled
			SetEvent(conn->hReady);

			// exit forever loop
			break;
		}
	}
}

/**
 * Watches for data on a live socket, buffering as it comes in
 */
void NetworkThreadSocket(NetworkManager *nm, Connection *conn) {
	char buffer[NETWORK_BUFLEN];
	int iResult;
	std::string lower = "";

	// Get host information
	hostent *host;
	unsigned long addr;


	// establish a network connection if needed
	if(conn->socket == NULL) {
		// open the socket using the provided information
		conn->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if(conn->socket == INVALID_SOCKET) {
			conn->socket = NULL;
	
			// finished initializing
			conn->settings.del("isInit");
			return;
		}

/*		// Open port anyways, even if it wasn't closed down cleanly before
		bool bOptVal = true;
		setsockopt(conn->socket, SOL_SOCKET, SO_REUSEADDR, (char*)&bOptVal, sizeof(bOptVal));
*/
		if(inet_addr(conn->settings.get("host")) == INADDR_NONE) {
			host = gethostbyname(conn->settings.get("host"));
		}
		else {
			addr = inet_addr(conn->settings.get("host"));
			host = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET);
		}

		// If we haven't been able to work with the provided host, abort
		if(host == NULL) {
			closesocket(conn->socket);
			conn->socket = NULL;

			// finished initializing
			conn->settings.del("isInit");
			return;
		}

		// save off this host name
		conn->set("host", host->h_name);

		// convert the host to lowercase
		lower = conn->get("host");
		std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
		conn->set("host", (char*)lower.c_str());

		// Connect to the server
		sockaddr_in server;

		server.sin_addr.s_addr = *((unsigned long*)host->h_addr);
		server.sin_family = AF_INET;
		server.sin_port = htons(atoi(conn->settings.get("port")));
		if(connect(conn->socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) {
			// Connect failed, abort
			closesocket(conn->socket);
			conn->socket = NULL;

			// finished initializing
			conn->settings.del("isInit");
			return;	
		}

		// store the readable IPv4 address
		conn->settings.set("hostip", inet_ntoa(server.sin_addr));
	}

	// socket connection established, mark as such
	conn->settings.set("isAlive", "true");

	// finished initializing
	conn->settings.del("isInit");

	// Set the isReady event to signaled
	SetEvent(conn->hReady);


	while(conn->settings.isSet("isAlive")) {
		// delay a bit if we are starting to get overwhelmed with incoming data
//		while(conn->bm->count > NETWORK_BUFLEN) {
//			log->write("8a\n");
//			printf("[tlnet:flood_aversion]");
//			Sleep(200);
//		}

		// wait for and buffer new data
		iResult = recv(conn->socket, buffer, NETWORK_BUFLEN, 0);

		// Three results are possible:
		// 1 - socket gracefully closed
		// 2 - socket error has occured
		// 3 - data was recieved
		if(iResult == 0) {
			// socket was gracefully closed
#ifdef _DEBUG
//			printf("[NetworkThread] Socket was gracefully closed.\n");
#endif // _DEBUG

			// client dropped connection, disable
			conn->settings.del("isAlive");
			conn->settings.set("isDel", "true");

			// Set the isReady event to signaled
			SetEvent(conn->hReady);
		}
		else if(iResult == SOCKET_ERROR) {
			// error has occured
#ifdef _DEBUG
			printf("[NetworkThread] Socket error: %d\n", WSAGetLastError());
#endif // _DEBUG

			// client dropped connection, disable
			conn->settings.del("isAlive");
			conn->settings.set("isDel", "true");

			// Set the isReady event to signaled
			SetEvent(conn->hReady);
		}
		else if(iResult > 0) {
			// addBytes always succeeds, though it will delay if the buffer is getting too full
			conn->bm->addBytes(buffer, iResult);

			if(conn->bm->isReady()) {
				// Set the isReady event to signaled
				SetEvent(conn->hReady);
			}
		}
	}
}

}